import javax.swing.Timer;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.Graphics2D;
import java.util.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.BasicStroke;
import java.awt.Cursor;
import java.awt.geom.Line2D;

public class MyWorld implements ActionListener {
   public final static int WIDTH = 600;  // in pixels
   public final static int HEIGHT = 500; // in pixels
   public final static int X_ORIGEN = (int)(WIDTH*0.1); // MyWorld space origen (x,y) will be on
   public final static int Y_ORIGEN = (int)(HEIGHT*0.9); // (X_ORIGEN,Y_ORIGEN) of the panel space.
   public final static AffineTransform SPACE_TRANSFORM;  // transforms (x,y) in (X,Y) of panel.
   public static AffineTransform SPACE_INVERSE_TRANSFORM; // transforms (X,Y) in (x,y) of my world.
   public static Line2D.Double X_AXIS;  // lines to draw my world axes (singular axis, plural axes).
   public static Line2D.Double Y_AXIS;
   private final static double AXES_SCALE = 100.0;  // 1 [m] equals 100 [pixels]
   
   static {
       SPACE_TRANSFORM = AffineTransform.getTranslateInstance(X_ORIGEN, Y_ORIGEN);
       SPACE_TRANSFORM.scale(AXES_SCALE,-AXES_SCALE);  // it also inverts direction of y-coordinate 
       try {
          SPACE_INVERSE_TRANSFORM = SPACE_TRANSFORM.createInverse();
       }catch (NoninvertibleTransformException e){ 
          System.exit(0);
       }
       X_AXIS = new Line2D.Double(-0.3,0, 3.0,0);  // each axis length is 3 [m] from origen.
       Y_AXIS = new Line2D.Double(0, -0.3, 0, 3.0);       
   }
   private double t;              // Simulation time
   private Timer pase;
   private double delta_t;        // in seconds
   private double refreshPeriod;  // in seconds
   
   private Vector2D g;
   private float viscosity;
   private PhysicsLabPanel labPanel;
   private Vector<PhysicsElement> elements;

   public MyWorld(){
      t = 0;
      refreshPeriod = 0.06;      // initial value 60 [ms]
      delta_t = 0.0001;          // initial value 0.1 [ms]
      pase = new Timer((int)(refreshPeriod*1000), this);
      g = new Vector2D(0, 0);  // start with no gravity
      viscosity = 0.0f;        // no friction
      elements = new Vector<PhysicsElement>();
      labPanel = null;
   }
   public void setPanel (PhysicsLabPanel p) {
      labPanel = p;
   }
   public void setDelta_t(double delta) {
      delta_t = delta;
   }
   public void setRefreshPeriod (double rp) {
      refreshPeriod = rp;
      pase.setDelay((int)(refreshPeriod*1000)); // convert from [s] to [ms]
   }
   public void setGravity (Vector2D gravity) {
      g = gravity;
   }
   public void setViscosity(float v){
      viscosity = v;
   }
   public void addElement(PhysicsElement e) {
      elements.add(e);
      labPanel.repaint();
   }
   /**
    * Método para iniciar una simulación. Si ya se habia iniciado una simulación antes,
    * ésta función se encarga de reiniciar todos los valores de velocidades y aceleraciones.
    */
   public void start() {
      if(pase.isRunning()) return;
      for (PhysicsElement e: elements){
      if(e instanceof GBall){
          ((Ball) e).reset_stat();}}
      labPanel.desableMouseListener();
      pase.restart();
   }
   /**
    * Método que permite continuar con la simulación, recuperando los datos sobre velocidades
    * y aceleraciones ya guardados.
    */
   public void resume() {
      if(pase.isRunning()) return;
      for (PhysicsElement e: elements)
         e.setInitialState(g, viscosity);
      labPanel.desableMouseListener();
      pase.start();
   }
   public void stop(){
      pase.stop();
      labPanel.enableMouseListener();
   }
   public void actionPerformed (ActionEvent event) {  // simulate passing time
      double nextStop=t+refreshPeriod;
      for (; t<nextStop; t+=delta_t){
      for (PhysicsElement e: elements)
         e.computeNextState(delta_t, g, viscosity);
      for (PhysicsElement e: elements)
         e.updateState();
      }
      labPanel.repaint();
   }
   public void paintComponent(Graphics2D g2d){
      g2d.drawString("ELO329 1er.Sem. 2011,   1 [m] = "+AXES_SCALE+" [pixels]", WIDTH/4, 30);
      g2d.transform(SPACE_TRANSFORM);
      g2d.setStroke(new BasicStroke(0.02f));
      g2d.draw(X_AXIS);
      g2d.draw(Y_AXIS);
      for (PhysicsElement e: elements)
         if (e instanceof Drawable)
            ((Drawable)e).draw(g2d);
   }
   public Drawable find(double x, double y) {
      for (PhysicsElement e: elements)
         if (e instanceof Drawable){
            Drawable de = (Drawable) e;
            if (de.contains(x,y)) return de;
         }
      return null;
   }

/**
 * elimina el objeto PhysicsElement e de la lista de objetos de MyWorld. Si éste es
 * de tipo Rubber o Spring, antes de eliminarse se desatará de cualquier objeto de tipo
 * AttachableElement.
 * @param e el elemento a eliminar.
 */
    public void remove(PhysicsElement e){
       if(e instanceof Rubber){
           for (PhysicsElement q: elements){
                if(q instanceof Ball) ((Ball) q).detachRubber((Rubber)e);
                if(q instanceof FixedHook) ((FixedHook) q).detachRubber((Rubber)e);}
       }
       if(e instanceof Spring){
           for (PhysicsElement q: elements){
                if(q instanceof Ball) ((Ball) q).detachSpring((Spring)e);
                if(q instanceof FixedHook) ((FixedHook) q).detachSpring((Spring)e);}
       }
       elements.remove(e);
   }
   /**
    * Cambia el cursor del mouse por el cursor CROSSHAIR_CURSOR. Esto sirve de señal
    * para el MouseListener para que éste pueda ejecutar el método remove.
    */
   public void delete(){
       labPanel.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
   }
   public void set(){
       labPanel.setCursor(new Cursor(Cursor.HAND_CURSOR));
   }

   public void edit(String data, PhysicsElement e){
       

   if(e instanceof Ball){((Ball)e).set_mass(Float.parseFloat(data));/*System.out.print(((Ball)e).get_mass());*/}
   if(e instanceof Spring){((Spring)e).set_stiffness(Float.parseFloat(data));}
   if(e instanceof Rubber){((Rubber)e).set_stiffness(Float.parseFloat(data));}

   }

   public AttachableElement findAttachableElement(double x, double y) {
      for (PhysicsElement e :elements)
         if (e instanceof AttachableElement) {
            AttachableElement ae =(AttachableElement) e;
            if (ae instanceof Drawable)
            if (((Drawable)ae).contains(x,y)) return ae;
         }
      return null;
   }
} 
